package logica;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.Hashtable;
import java.util.Iterator;
import persistencia.XML;
import value_objects.VoDatosPreinicio;
import value_objects.VoDatosPreinicioCarga;
import value_objects.VoDisparo;
import value_objects.VoInfoBando;
import value_objects.VoInfoJuego;
import value_objects.VoInfoPuerto;
import value_objects.VoInfoPuertoEnJuego;
import value_objects.VoInicioJuego;
import value_objects.VoJugador;
import value_objects.VoJugadorListado;
import value_objects.VoNombreArma;
import value_objects.VoPartidaListado;
import value_objects.VoTextoIdioma;
import auxiliares.Configuracion;
import auxiliares.Coordenada;
import auxiliares.Global;
import excepciones.JuegoException;

public class Juego {
	// Constantes
	static final int CANT_MAX_JUEGOS = 10;
	static final int CANT_MAX_JUGADORES = 3;
	static final int PUERTO_ORIENTAL = 1;
	static final int PUERTO_OCCIDENTAL = 2;
	static final String BANDO_INICIAL = "Rojo";
	public static final String RUTA_JUEGO = "/silverRiver/";
	private static final long LIMITE_ABANDONO = 5000; // 5 segundos
	private static final long LIMITE_PARTIDA_SIN_INICIAR = 600000; // 10 minutos
	private static final long LIMITE_PARTIDA_SIN_REFRESCO = 15000; // 15 segundos
	
	// Atributos estáticos
	private static Hashtable<String, Juego> hashJuegos;

	// Atributos de instancia
	private long fechaCreado;
	private long ultimoRefresco;
	private String nombre;
	private Hashtable <String, Jugador> hashJugadores;
	private String codigoJuego;
	private int nivelDificultad;
	private boolean iniciado=false;
	private Hashtable<Integer,Puerto> hashPuertos;  
	private Hashtable<String, Disparo> disparosActivos;
	private String creadorPartida;
	private int cantidadJugadoresUnidos;
	private boolean cargado;
	private boolean estaRojoCargado;
	private boolean estaAzulCargado;
	private boolean estaVerdeCargado;
	private Hashtable<String, Long> ultimasPeticiones;
	private Hashtable<String, String> codigosGuardado;
	
	/**
	 * Constructor normal a partir del codigo de juego, nombre y dificultad
	 * @param codigoJuego
	 * @throws JuegoException 
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	public Juego(String codigoJuego, String _nombrePartida, Integer _dificultad) throws JuegoException{
		this.codigoJuego = codigoJuego;
		nombre = _nombrePartida;
		nivelDificultad = _dificultad;
		this.hashJugadores = new Hashtable<String, Jugador>();
		this.hashPuertos= new Hashtable<Integer, Puerto>();
		disparosActivos = new Hashtable<String, Disparo>();
		ultimasPeticiones = new Hashtable<String, Long>();
		codigosGuardado = new Hashtable<String, String>();
		estaAzulCargado = false;
		estaRojoCargado = false;
		estaVerdeCargado = false;
		cargado = false;
		cantidadJugadoresUnidos = 0;
		fechaCreado = System.currentTimeMillis();
		crearPuertos();
	}

	/**
	 * Crea todos los puertos del juego
	 * @throws JuegoException
	 */
	public void crearPuertos() throws JuegoException {
		try {
			Puerto puerto1, puerto2;
			//puerto1 = Puerto.crearPuertoSegunTipo(PUERTO_ORIENTAL);
			puerto1 = new Puerto(PUERTO_ORIENTAL);
			//Puerto puerto2 = Puerto.crearPuertoSegunTipo(PUERTO_OCCIDENTAL);
			puerto2 = new Puerto(PUERTO_OCCIDENTAL);
			hashPuertos.put(puerto1.getIdPuerto(), puerto1);
			hashPuertos.put(puerto2.getIdPuerto(), puerto2);
		} catch (Exception e) {
			e.printStackTrace();
			throw new JuegoException(JuegoException.ERROR_INESPERADO);
		}
	};
	
	/**
	 * Constructor normal a partir del código de juego
	 * @param codigoJuego
	 * @throws JuegoException 
	 */
	public Juego(String codigoJuego) throws JuegoException {
		this.codigoJuego = codigoJuego;
		this.hashJugadores = new Hashtable<String, Jugador>();
		this.hashPuertos= new Hashtable<Integer, Puerto>();
		disparosActivos = new Hashtable<String, Disparo>();
		ultimasPeticiones = new Hashtable<String, Long>();
		codigosGuardado = new Hashtable<String, String>();
		estaAzulCargado = false;
		estaRojoCargado = false;
		estaVerdeCargado = false;
		cargado = false;
		cantidadJugadoresUnidos = 0;
		fechaCreado = System.currentTimeMillis();
		nivelDificultad = 1;
		nombre = new String();
		crearPuertos();
	}

	/**
	 * Agrega un jugador a una partida creada (no cargada) 	
	 * @param _nombreJugador
	 * @param _nombreBando
	 * @param _idioma
	 * @return
	 * @throws JuegoException 
	 */
	public void agregarJugador(String _nombreJugador, String _nombreBando, String _idioma) throws JuegoException {
		if (!(getIniciado())) {
			if (getCantidadJugadores() < CANT_MAX_JUGADORES) {
				if (!hashJugadores.containsKey(_nombreJugador)) {
					if (!existeNombreBandoEnPartida(_nombreBando)) {
						Jugador jugador = new Jugador();
						jugador.setNombre(_nombreJugador);
						jugador.setIdioma(_idioma);
						try {
							Bando bando = new Bando(_nombreBando, nivelDificultad);
							jugador.setBando(bando);
							this.hashJugadores.put(_nombreJugador, jugador);
						} catch (Exception e) {
							e.printStackTrace();
							throw new JuegoException(JuegoException.ERROR_INESPERADO);
						}
						if (creadorPartida == null) creadorPartida = _nombreJugador;
					} else {
						throw new JuegoException(JuegoException.ERROR_BANDO_EXISTE);
					}
				} else {
					throw new JuegoException(JuegoException.ERROR_JUGADOR_EXISTE);
				}
			} else {
				throw new JuegoException(JuegoException.ERROR_JUEGO_LLENO);
			}
		} else {
			throw new JuegoException(JuegoException.ERROR_PARTIDA_INICIADA);
		}
	}

	/**
	 * Determina si existe un jugador utilizando un nombre de bando en la partida 
	 * @param _nombreBando
	 * @return
	 */
	private boolean existeNombreBandoEnPartida(String _nombreBando) {
		boolean existe = false;
		Iterator<Jugador> jugadores = getJugadores();
		while (jugadores.hasNext() && !existe) {
			if (jugadores.next().getBando().getNombreBando().equalsIgnoreCase(_nombreBando)) { 
				existe = true;
			}
		}
		return existe;
	}

	/**
	 * Marca la partida como iniciada.
	 * @throws JuegoException 
	 */
	public void iniciarPartida() throws JuegoException {
		if (sePuedeIniciarPartida()) {
			setIniciado(true);
		} else {
			throw new JuegoException(JuegoException.ERROR_INICIAR_PARTIDA);
		}
	}

	/**
	 * Determina si se puede, o no 
	 * @return
	 */
	private boolean sePuedeIniciarPartida() {
		boolean estaRojo = false;
		boolean estaAzul = false;
		boolean estanAmbos = false;
		if (!getIniciado()) {
			Iterator<Jugador> jugadores = getJugadores();
			while (jugadores.hasNext() && (!estanAmbos)) {
				Jugador jug = jugadores.next();
				if (jug.getBando().getNombreBando().equalsIgnoreCase("Azul")) {
					estaAzul = true;
				} else if (jug.getBando().getNombreBando().equalsIgnoreCase("Rojo")) {
					estaRojo = true;
				}
				estanAmbos = estaAzul & estaRojo;
			}
		}
		return estaAzul && estaRojo;
	}
	
	/**
	 * Guarda la partida y genera un xml con la info que se descarga el creador de la partida
	 * @throws JuegoException
	 */
	public void guardarPartida() throws JuegoException {
		try {
			(new XML()).guardarJuego(this);
			Iterator<Jugador> it = getJugadores();
			while (it.hasNext()) {
				Jugador jug = it.next();
				if (jug.getNombre() == creadorPartida) {
					codigosGuardado.put(jug.getBando().getNombreBando(), codigoJuego);
				} else {
					codigosGuardado.put(jug.getBando().getNombreBando(), codigoJuego + jug.getBando().getNombreBando());
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			throw new JuegoException(JuegoException.ERROR_INESPERADO);
		}
	}
	
	/**
	 * Permite abandonar la partida
	 * @param nombreBando
	 */
	public void abandonarPartida(String _nombreBando) {
		Iterator<Jugador> jugadores =  getJugadores();
		boolean terminado = false;
		while (jugadores.hasNext() && !terminado) {
			Jugador jugador = jugadores.next();

			if (jugador.getBando().getNombreBando().equalsIgnoreCase(_nombreBando)) {
				jugador.setAbandono(true);
				jugador.getBando().matarBarcos();
				terminado = true;
			}
		}
	}
	
	/**
	 * Devuelve un value object con una instantánea del juego
	 * @return VoInfoJuego
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	public VoInfoJuego obtenerInfoJuego(String _bando) throws FileNotFoundException, IOException {
		ultimasPeticiones.put(_bando, System.currentTimeMillis());
		ultimoRefresco = System.currentTimeMillis();
		Iterator<Puerto> puertos = getPuertos();
		VoInfoPuertoEnJuego[] infoPuertos = new VoInfoPuertoEnJuego[hashPuertos.size()];
		int i = 0;
		while (puertos.hasNext()) {
			Puerto port = puertos.next();
			infoPuertos[i] = new VoInfoPuertoEnJuego(port.getIdPuerto(), port.getOcupado());
			i++;
		}
		Iterator<Jugador> jugadores =  getJugadores();
		VoInfoBando[] infoBandos = new VoInfoBando[hashJugadores.size()];
		i = 0;
		while (jugadores.hasNext()) {
			  Jugador jugador = jugadores.next();
			  Long ultima = ultimasPeticiones.get(jugador.getBando().getNombreBando());
			  if (ultima != null && System.currentTimeMillis() - ultima > LIMITE_ABANDONO) {
				  jugador.setAbandono(true);
			}
			/*if (!jugador.getAbandono()) {*/
				infoBandos[i] = jugador.obtenerInfoBando();
			//}
			i++;
		}
		VoDisparo[] disparos = new VoDisparo[disparosActivos.size()];
		Iterator<Disparo> itDisparo = disparosActivos.values().iterator();
		i = 0;
		while (itDisparo.hasNext()) {
			Disparo disparo = itDisparo.next();
			if(disparo.tieneBando(_bando)){
				disparos[i++] = new VoDisparo(
					disparo.getNombreBarco(), disparo.getCodigoArma(), disparo.getOrigen(), disparo.getDestino()
				);
				disparo.quitarBando(_bando);
			}
		}

		String codigoGuardado = "";
		if (codigosGuardado.containsKey(_bando)) {
			codigoGuardado = codigosGuardado.get(_bando);
			codigosGuardado.remove(_bando);
		}

		return new VoInfoJuego(infoBandos, disparos, infoPuertos, determinarGanador(), codigoGuardado);
	}
	
	/**
	 * Determina si hay un bando ganador. 
	 * @return String El nombre del bando ganador. Si no hay ganador devuelve null
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	private String determinarGanador() throws FileNotFoundException, IOException {
		
		Configuracion k = Configuracion.getInstancia(Configuracion.CONFIGURACION_POSICIONES);
		
		int posicionX = Integer.parseInt(k.getProperty("ViaDeEscapeCoordX").toString().trim());
		int posicionY = Integer.parseInt(k.getProperty("ViaDeEscapeCoordY").toString().trim());
		int distancia = Integer.parseInt(k.getProperty("ViaDeEscapeDistancia").toString().trim());
		
		String ganador = null;
		
		// Me creo una lista con los jugadores que no abandonaron y tienen barcos en juego
		Iterator<Jugador> jugadores =  getJugadores();
		ArrayList<String> enJuego = new ArrayList<String>();
		while (jugadores.hasNext()) {
			Jugador jugador = jugadores.next();
			Boolean tieneBarcos = jugador.getBando().cantidadBarcosEnJuego() > 0; 

			if (!jugador.getAbandono() && tieneBarcos) {
				//EVALUAMOS SI EL BARCO ROJO ESTA EN LA ZONA DE ESCAPE
				if(jugador.getBando().getNombreBando().equalsIgnoreCase(Bando.BANDO_ROJO)){
					Barco barcoRojo = jugador.getBando().getBarco(Barco.BARCO_ROJO1); 
					if(posicionX - distancia <= barcoRojo.getPosicion().getCoordX() && 
					   posicionY - distancia <= barcoRojo.getPosicion().getCoordY()) {
						return Bando.BANDO_ROJO;
					}
				}
				enJuego.add(jugador.getNombre());
			}
		}
		
		
		// Verfico la posibilidad de que hayan dos bandos, siendo uno el verde y el otro tenga un unico barco en puerto
		Boolean hayVerde = false;
		int indiceVerde = 0;
		while (indiceVerde < enJuego.size() && !hayVerde) {
			if (hashJugadores.get(enJuego.get(indiceVerde)).getBando().getNombreBando() == Bando.BANDO_VERDE) {
				hayVerde = true;
			} else {
				indiceVerde++;
			}
		}
		if (hayVerde && enJuego.size() == 2) {
			Integer indiceJugadorNoVerde = indiceVerde == 0? 1 : 0;
			if (hashJugadores.get(enJuego.get(indiceJugadorNoVerde)).getBando().tieneBarcoEnPuerto()) {
				enJuego.remove(indiceJugadorNoVerde);
			} else {
				enJuego.remove(indiceVerde);
			}
		}
		// Si luego de analizar las posibilidades me queda un sólo bando en juego, ese es el ganador
		if (enJuego.size() == 1) {
			ganador = hashJugadores.get(enJuego.get(0)).getBando().getNombreBando();
		} 
		return ganador;
	}

	/**
	 * Envía los datos de inicio del juego.
	 * @param _nombreBando
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public VoInicioJuego datosInicio(String _nombreBando) throws FileNotFoundException, IOException {
		Iterator<Jugador> itJugadores = hashJugadores.values().iterator();
		VoJugador[] jugadores = new VoJugador[getCantidadJugadores()];
		VoInfoBando[] infoBandos = new VoInfoBando[hashJugadores.size()];
		Jugador yomismo = null;

		int i = 0;
		while (itJugadores.hasNext()) {
			Jugador jugador = itJugadores.next();
			jugadores[i] = new VoJugador(jugador.getNombre(), jugador.getBando().getNombreBando());
			infoBandos[i++] = jugador.obtenerInfoBando();
			if (jugador.getBando().getNombreBando().equalsIgnoreCase(_nombreBando)) {
				yomismo = jugador;
			}
		}
		Iterator<Puerto> puertos = getPuertos();
		VoInfoPuerto[] infoPuertos = new VoInfoPuerto[hashPuertos.size()];
		i = 0;
		while (puertos.hasNext()) {
			infoPuertos[i++] = puertos.next().obtenerInfoPuerto();
		}
		Configuracion idioma = Configuracion.getInstancia(Configuracion.rutaArchivoIdiomas(yomismo.getIdioma()));
		Enumeration<Object> claves = idioma.getKeys();
		VoTextoIdioma[] textos = new VoTextoIdioma[idioma.getCount()];
		i = 0;
		while (claves.hasMoreElements()) {
			String clave = (String) claves.nextElement();
			textos[i++] = new VoTextoIdioma(clave, idioma.getProperty(clave));
		}
		
		VoNombreArma[] nombresArmas = Arma.getNombresArmas();
		return new VoInicioJuego(textos, jugadores, infoBandos, null, infoPuertos, determinarGanador(), nombresArmas);
	}
	
	/**
	 * Mueve un barco a un ángulo y posicion dadas 
	 * @param _nombreJugador
	 * @param _nombreBarco
	 * @param _xy
	 * @param _angulo
	 */
	public void movimientoBarco (String _nombreJugador, String _nombreBarco, Coordenada _xy, int _angulo) {
		getJugador(_nombreJugador).getBando().getBarco(_nombreBarco).setPosicion(_xy);
		getJugador(_nombreJugador).getBando().getBarco(_nombreBarco).setSentidoPosicion(_angulo);
	}
	
	/**
	 * Vira el barco a un ángulo dado
	 * @param _nombreJugador
	 * @param _nombreBarco
	 * @param _angulo
	 */
	public void virarBarco (String _nombreJugador, String _nombreBarco, int _angulo) {
		getJugador(_nombreJugador).getBando().getBarco(_nombreBarco).setSentidoPosicion(_angulo);
	}

	/**
	 * Permite el ataque de un barco
	 * @param _nombreJugador
	 * @param _x
	 * @param _y
	 * @param _nombreBarcoAtaca
	 * @param _idArma
	 */
	public void ataqueBarco(String _nombreJugador, String _nombreBarcoAtaca, Integer _idArma,
							Integer _xO, Integer _yO, Integer _xD, Integer _yD) {
		//Tomo datos del barco que dispara en la variable Barco
		if (getJugador(_nombreJugador).getBando().getBarco(_nombreBarcoAtaca).getArma(_idArma).getCantMunicionActual() > 0) {
			agregarDisparoActivo(new Disparo(_nombreBarcoAtaca,	_idArma,new Coordenada(_xO, _yO),new Coordenada(_xD, _yD)));
			getJugador(_nombreJugador).getBando().getBarco(_nombreBarcoAtaca).getArma(_idArma).setCantMunicionActual(
					getJugador(_nombreJugador).getBando().getBarco(_nombreBarcoAtaca).getArma(_idArma).getCantMunicionActual() - 1
			);
		}
	}

	/**
	 * Agrega un disparo al juego. Se enviará a los clientes en los sucesivos refrescos.
	 * @param disparo
	 */
	public void agregarDisparoActivo(Disparo disparo) {
		Iterator<Jugador> jugadores = getJugadores();
		ArrayList<String> bandos = new ArrayList<String>();
		while(jugadores.hasNext()){
			bandos.add(jugadores.next().getBando().getNombreBando());
		}
		disparo.setBandos(bandos);
		this.disparosActivos.put(disparo.getNombreBarco() + disparo.getCodigoArma().toString(), disparo);
	}
	
	/**
	 * Obtiene un disparo activo a partir del barco que disparó y con qué arma lo hizo.
	 * @param _nombre
	 * @param _idArma
	 * @return
	 */
	public Disparo obtenerDisparoActivo(String _nombre, String _idArma) {
		return this.disparosActivos.get(_nombre + _idArma);
	}
	
	/**
	 * Elimina un disparo activo del juego.
	 * @param _nombre
	 * @param _idArma
	 * @return
	 */
	public void eliminarDisparoActivo(String _nombre, Integer _idArma) {
		this.disparosActivos.remove(_nombre + _idArma.toString());
	}

	/**
	 * Hace las acciones necesarias cuando un barco es alcanzado por un proyectil.
	 * @param _nombreBarcoRecibe
	 * @param _nombreBarcoAtaca
	 * @param _idArma
	 * @throws JuegoException 
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public void colisionArmaBarco(String _nombreBarcoRecibe, String _nombreBarcoAtaca, int _idArma) throws JuegoException {
		Iterator<Jugador> itJugadores = getJugadores();
		int blindaje = 0, blindajeMax = 0;
		Barco ataca = null, recibe = null;

		while (itJugadores.hasNext()) {
			Jugador jugador = itJugadores.next();
			if (jugador.getBando().getBarco(_nombreBarcoRecibe) != null) {
				recibe = jugador.getBando().getBarco(_nombreBarcoRecibe);
			} 
			if (jugador.getBando().getBarco(_nombreBarcoAtaca) != null) { 
				ataca =  jugador.getBando().getBarco(_nombreBarcoAtaca);
			}
		}
		try {
			blindajeMax = recibe.darBlindajeMaximo();
			blindaje = recibe.getBlindajeActual();
			
			recibe.setVida((int)Math.round(recibe.getVida() * ((blindaje * 100.0 / blindajeMax) / 100.0)));
			recibe.setVelocidadActual(recibe.getVelocidadActual() + ataca.getArma(_idArma).getDanioVelocidad());
			recibe.setBlindajeActual(recibe.getBlindajeActual() - ataca.getArma(_idArma).getDanioBlindaje());
			
			if (recibe.getVelocidadActual() < 0) {
				recibe.setVelocidadActual(0);
			}
			if (recibe.getBlindajeActual() < 0) {
				recibe.setBlindajeActual(0);
			}
			
			// Falta dañar armas
			disparosActivos.remove(_idArma);
		} catch (Exception e) {
			e.printStackTrace();
			throw new JuegoException(JuegoException.ERROR_INESPERADO);
		}
	}
	
	/**
	 * Devuelve los datos que se muestran durante la espera para cargar partida.
	 * @return
	 */
	public VoDatosPreinicio datosPreinicio() {
		Iterator<Jugador> itJugadores = hashJugadores.values().iterator();
		VoJugador[] jugadores = new VoJugador[getCantidadJugadores()];
		
		int i = 0;
		while (itJugadores.hasNext()) {
			Jugador jugador = itJugadores.next();
			jugadores[i] = new VoJugador(jugador.getNombre(), jugador.getBando().getNombreBando());
			i++;
		}
		return new VoDatosPreinicio(iniciado, jugadores);
	}
	
	/**
	 * Devuelve los datos de necesarios para la espera de jugadores.
	 * @return
	 */
	public VoDatosPreinicioCarga datosPreinicioCarga() {
		return new VoDatosPreinicioCarga(cantidadJugadoresUnidos, hashJugadores.size(), iniciado);
	}

	/**
	 * Agrega un jugador (a partir de su bando) a una partida que fue cargada.
	 * @param _nombreBando
	 * @throws JuegoException
	 */
	public void agregarJugadorPartidaCargada(String _nombreBando) throws JuegoException {
		if (!getIniciado()) {
			if (cantidadJugadoresUnidos <= getCantidadJugadores()) {
				if (existeNombreBandoEnPartida(_nombreBando)) {
					if (!existeBandoCargado(_nombreBando)) {
						agregarBandoComoCargado(_nombreBando);
					} else { 
						throw new JuegoException(JuegoException.ERROR_BANDO_EXISTE);
					}
				} else { 
					throw new JuegoException(JuegoException.ERROR_BANDO_NO_EXISTE);
				}
			} else { 
				throw new JuegoException(JuegoException.ERROR_JUEGO_LLENO);
			}
		} else { 
			throw new JuegoException(JuegoException.ERROR_PARTIDA_INICIADA);
		}
	}

	/**
	 * Agrega un bando a una partida que fue cargada
	 * @param _nombreBando
	 */
	private void agregarBandoComoCargado(String _nombreBando) {
		if (_nombreBando.equalsIgnoreCase(Bando.BANDO_ROJO)) {
			estaRojoCargado = true;
		} else if (_nombreBando.equalsIgnoreCase(Bando.BANDO_AZUL)) {
			estaAzulCargado = true;
		} else if (_nombreBando.equalsIgnoreCase(Bando.BANDO_VERDE)) {
			estaVerdeCargado = true;
		}
		cantidadJugadoresUnidos++;
	}

	/**
	 * Determina si ya se unió un bando a una partida cargada
	 * @param _nombreBando
	 * @return
	 */
	private boolean existeBandoCargado(String _nombreBando) {
		boolean respuesta = false;
		if (_nombreBando.equalsIgnoreCase(Bando.BANDO_ROJO)) {
			respuesta = getEstaRojoCargado();
		} else if (_nombreBando.equalsIgnoreCase(Bando.BANDO_AZUL)) {
			respuesta = getEstaAzulCargado();
		} else if (_nombreBando.equalsIgnoreCase(Bando.BANDO_VERDE)) {
			respuesta = getEstaVerdeCargado();
		}
		return respuesta;
	}

	/**
	 * Repara un barco en puerto a los valores permitidos según el puerto.
	 * @param _idPuerto
	 * @param _nombreBarco
	 * @param _reparacion
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public void repararEnPuerto(Integer _idPuerto, String _nombreBarco, String _reparacion) throws FileNotFoundException, IOException {
		Puerto puerto = hashPuertos.get(_idPuerto);
		boolean encontreBarco = false;
		Barco barco = null; 
		Iterator<Jugador> jugadores = getJugadores();
		while (jugadores.hasNext() && !encontreBarco) {
			Iterator<Barco> barcos = jugadores.next().getBando().getBarcos();
			while (barcos.hasNext() && !encontreBarco) {
				barco = barcos.next();
				if (barco.getNombre().equalsIgnoreCase(_nombreBarco)) {
					encontreBarco = true;
				}
			}
		}
		if (puerto.getLimiteReparaciones() == 3) {
			int blindajeReparado = (int)((puerto.getReparBlindaje() / 100.0) * (barco.darBlindajeMaximo()  - barco.getBlindajeActual()));
			int velocidadReparada = (int)(puerto.getReparVelocidad() / 100.0) * (barco.darVelocidadMaxima() - barco.getVelocidadActual());
			barco.setBlindajeActual(blindajeReparado + barco.getBlindajeActual());
			barco.setVelocidadActual(velocidadReparada + barco.getVelocidadActual());
			Iterator<Arma> armas = barco.getArmas();
			while (armas.hasNext()) {
				Arma arma = armas.next();
				int alcanceMaximoReparado = (int)((puerto.getReparArma().getDanioAlncance() / 100.0) * (arma.darAlcanceMaximo() - arma.getAlcanceActual()));
				int cantMunicionesRepardado = (int)((puerto.getReparArma().getDanioCantMuniciones() / 100.0) * (arma.darCantMunicionMaximo() - arma.getCantMunicionActual()));
				arma.setAlcanceActual(alcanceMaximoReparado + arma.getAlcanceActual());
				arma.setCantMunicionActual(cantMunicionesRepardado + arma.getCantMunicionActual());
			}
		} else {
			if (_reparacion.equalsIgnoreCase(Barco.PROPIEDAD_ARMA)) {
				Iterator<Arma> armas = barco.getArmas();
				while (armas.hasNext()) {
					Arma arma = armas.next();
					int alcanceMaximoReparado = (int)((puerto.getReparArma().getDanioAlncance() / 100.0) * (arma.darAlcanceMaximo() - arma.getAlcanceActual()));
					int cantMunicionesRepardado = (int)((puerto.getReparArma().getDanioCantMuniciones() / 100.0) * (arma.darCantMunicionMaximo() - arma.getCantMunicionActual()));
					arma.setAlcanceActual(alcanceMaximoReparado + arma.getAlcanceActual());
					arma.setCantMunicionActual(cantMunicionesRepardado + arma.getCantMunicionActual());
				}
			} else if (_reparacion.equalsIgnoreCase(Barco.PROPIEDAD_VELOCIDAD)) {
				int velocidadReparada = (int)(puerto.getReparVelocidad() / 100.0) * (barco.darVelocidadMaxima() - barco.getVelocidadActual());
				barco.setVelocidadActual(velocidadReparada + barco.getVelocidadActual());
			} else if (_reparacion.equalsIgnoreCase(Barco.PROPIEDAD_BLINDAJE)) {
				int blindajeReparado = (int)((puerto.getReparBlindaje() / 100.0) * (barco.darBlindajeMaximo() - barco.getBlindajeActual()));
				barco.setBlindajeActual(blindajeReparado + barco.getBlindajeActual());
			}
		}
	}

	// ********************* GETTERS Y SETTERS *********************

	public long getUltimoRefresco() {
		return ultimoRefresco;
	}
	
	public String getCreadorPartida() {
		return creadorPartida;
	}

	public void setCreadorPartida(String creadorPartida) {
		this.creadorPartida = creadorPartida;
	}
	
	public int getCantidadJugadoresUnidos() {
		return cantidadJugadoresUnidos;
	}

	public void setCantidadJugadoresUnidos(int cantidadJugadoresUnidos) {
		this.cantidadJugadoresUnidos = cantidadJugadoresUnidos;
	}
	
	public long getFechaCreado() {
		return fechaCreado;
	}
	
	public void setCargado(Boolean _cargado) {
		cargado = _cargado;
	}
	
	public Boolean getCargado() {
		return cargado;
	}
	
	public void setPuerto(Puerto puerto) {
		this.hashPuertos.put(puerto.getIdPuerto(), puerto);
	}
	
	public void setJugador(Jugador jugador) {
		this.hashJugadores.put(jugador.getNombre(), jugador);
	}

	public Iterator<Disparo> getDisparos() {
		return disparosActivos.values().iterator();
	}
	
	public int getCantidadDisparos() {
		return disparosActivos.size();
	}
	
	public boolean getEstaRojoCargado() {
		return estaRojoCargado;
	}

	public void setEstaRojoCargado(boolean estaRojoCargado) {
		this.estaRojoCargado = estaRojoCargado;
	}

	public boolean getEstaAzulCargado() {
		return estaAzulCargado;
	}

	public void setEstaAzulCargado(boolean estaAzulCargado) {
		this.estaAzulCargado = estaAzulCargado;
	}

	public boolean getEstaVerdeCargado() {
		return estaVerdeCargado;
	}

	public void setEstaVerdeCargado(boolean estaVerdeCargado) {
		this.estaVerdeCargado = estaVerdeCargado;
	}
	
	public String getNombre() {
		return nombre;
	}

	public void setNombre(String _nombre) {
		this.nombre = _nombre;
	}

	public String getCodigoJuego() {
		return codigoJuego;
	}

	public void setCodigoJuego(String _codigoJuego) {
		this.codigoJuego = _codigoJuego;
	}
	
	public int getNivelDificultad() {
		return nivelDificultad;
	}

	public void setNivelDificultad(int nivelDificultad) {
		this.nivelDificultad = nivelDificultad;
	}

	public boolean getIniciado() {
		return iniciado;
	}

	public void setIniciado(boolean iniciado) {
		this.iniciado = iniciado;
	}
	
	public Jugador getJugador(String _nombreJugador) {
		return this.hashJugadores.get(_nombreJugador);
	}
	
	public Iterator <Jugador> getJugadores() {
		return hashJugadores.values().iterator();
	}
	
	public Iterator <Puerto> getPuertos() {
		return hashPuertos.values().iterator();
	}
	
	public int getCantidadJugadores() {
		return hashJugadores.size();
	}
	
	// ********************* Métodos estáticos *********************
	/**
	 * Limpia partidas antiguas que se crearon y no se iniciaron
	 */
	public static void eliminarPartidasViejas() {
		Iterator<Juego> juegos = hashJuegos.values().iterator();
		while (juegos.hasNext()) {
			Juego juego = juegos.next();
			if (!juego.getIniciado() && System.currentTimeMillis() - juego.getFechaCreado() > LIMITE_PARTIDA_SIN_INICIAR) {
				hashJuegos.remove(juego.getCodigoJuego());
			} else if (System.currentTimeMillis() - juego.getUltimoRefresco() > LIMITE_PARTIDA_SIN_REFRESCO) {
				hashJuegos.remove(juego.getCodigoJuego());
			}
		}
	}
	
	/**
	 * Inicializa el hash de juegos para todos los métodos estáticos.
	 * (de paso setea la ruta del juego)
	 */
	private static void inicializarColeccion() {
		if (hashJuegos == null) {
			hashJuegos = new Hashtable<String, Juego>();
			// De paso detecto el path del usuario
			Global.getInstance().setProperty(Global.SILVER_RIVER_PATH, System.getProperty("user.home") + RUTA_JUEGO);
		}
	}

	/**
	 * Deuelve una instancia de juego a partir de su código. 
	 * Para ser llamado cuando la partida está creada.
	 * @param codigoJuego
	 * @return
	 * @throws JuegoException
	 */
	public static Juego getInstance(String codigoJuego) throws JuegoException {
		inicializarColeccion();
		if (!hashJuegos.containsKey(codigoJuego)) {
			throw new JuegoException(JuegoException.ERROR_PARTIDA_NO_EXISTE);
		} 
		return hashJuegos.get(codigoJuego);
	}
	
	/**
	 * Crea una instancia de juego y devuelve esa instancia. 
	 * Para ser llamado al momento de crear una partida.
	 * @param codigoJuego
	 * @param _nombrePartida
	 * @param _dificultad
	 * @return
	 * @throws JuegoException
	 */
	public static Juego getInstance(String codigoJuego, String _nombrePartida, Integer _dificultad) throws JuegoException {
		inicializarColeccion();
		eliminarPartidasViejas();
		if (!hashJuegos.containsKey(codigoJuego)) {
			if (hashJuegos.size() < CANT_MAX_JUEGOS) {
				hashJuegos.put(codigoJuego, new Juego(codigoJuego, _nombrePartida, _dificultad));
			} else {
				throw new JuegoException(JuegoException.ERROR_SLOTS_LLENOS);
			}
		} else {
			throw new JuegoException(JuegoException.ERROR_PARTIDA_EXISTE);
		}
		return hashJuegos.get(codigoJuego);
	}
	
	/**
	 * Carga una partida guardada, la instancia y devuelve esa instancia.
	 * @param codigoJuego
	 * @param _nombrePartida
	 * @param _dificultad
	 * @return
	 * @throws JuegoException
	 */
	public static Juego getInstanceCargarPartida(String codigoJuego) throws JuegoException {
		inicializarColeccion();
		if (!hashJuegos.containsKey(codigoJuego)) {
			if (hashJuegos.size() < CANT_MAX_JUEGOS) {
				try {
					Juego juego = new Juego(codigoJuego);
					(new XML()).cargarPartida(juego);
					juego.setCargado(true);
					juego.agregarJugadorPartidaCargada(juego.getJugador(juego.getCreadorPartida()).getBando().getNombreBando());
					
					hashJuegos.put(codigoJuego, juego);
				} catch (Exception e) {
					e.printStackTrace();
					throw new JuegoException(JuegoException.ERROR_LEER_XML);
				}
			} else {
				throw new JuegoException(JuegoException.ERROR_SLOTS_LLENOS);
			}
		} else {
			throw new JuegoException(JuegoException.ERROR_PARTIDA_EXISTE);
		}
		return hashJuegos.get(codigoJuego);
	}
	
	/**
	 * Determina si existe un nombre de partida en el sistema.
	 * @param _nombrePartida
	 * @return
	 */
	public static boolean existeNombrePartida(String _nombrePartida) {
		boolean existe = false;
		inicializarColeccion();
		Iterator<Juego> juegos = hashJuegos.values().iterator();
		
		while(juegos.hasNext() && !existe) {
			Juego juego = juegos.next();
			existe = juego.getNombre().equalsIgnoreCase(_nombrePartida);
		}
		return existe;
	}
	
	/**
	 * Devuelve un listado de todas las partidas en formato array de VoPartida
	 * @return VoPartida[]
	 */
	static public VoPartidaListado[] listarPartidas() {
		inicializarColeccion();
		Iterator<Juego> it = hashJuegos.values().iterator();
		VoPartidaListado[] ret = new VoPartidaListado[hashJuegos.size()];

		int index = 0;
		while (it.hasNext()) {
			Juego juego = it.next();
			Iterator<Jugador> itJugadores = juego.getJugadores();
			VoJugadorListado[] jugadores = new VoJugadorListado[juego.getCantidadJugadores()];

			int i = 0;
			while (itJugadores.hasNext()) {
				Jugador jugador = itJugadores.next();

				boolean unido =
					jugador.getBando().getNombreBando().equalsIgnoreCase(Bando.BANDO_AZUL) && juego.getEstaAzulCargado() ||
					jugador.getBando().getNombreBando().equalsIgnoreCase(Bando.BANDO_ROJO) && juego.getEstaRojoCargado() ||
					jugador.getBando().getNombreBando().equalsIgnoreCase(Bando.BANDO_VERDE) && juego.getEstaVerdeCargado();

				jugadores[i++] = new VoJugadorListado(
					jugador.getNombre(), unido, jugador.getIdioma(), jugador.getBando().getNombreBando()
				);
			}

			VoPartidaListado vobj = new VoPartidaListado(
				juego.getCodigoJuego(), juego.getNombre(), jugadores, juego.getIniciado(), juego.getCargado()
			);
			ret[index++] = vobj;
		}
		return ret;
	}
	
	/***
	 * Genera el codigo de la partida concatenando el nombre de la partida con la fecha
	 * @param _nombreJuego
	 * @return
	 * @autor Maximiliano Anastasiadis
	 */
	public static String generarCodigoJuego(String _nombreJuego) {
		GregorianCalendar fecha = new GregorianCalendar();
		//Obtengo los parametros de la fecha
		String anioActual = String.valueOf(fecha.get(Calendar.YEAR));
		String mesActual = String.valueOf(fecha.get(Calendar.MONTH) + 1);
		String diaActual = String.valueOf(fecha.get(Calendar.DAY_OF_MONTH));
		String horaActual = String.valueOf(fecha.get(Calendar.HOUR_OF_DAY));
		String minutoActual = String.valueOf(fecha.get(Calendar.MINUTE));
		String segundoActual = String.valueOf(fecha.get(Calendar.SECOND));
		String milisegundoActual = String.valueOf(fecha.get(Calendar.MILLISECOND));
		// Convierto la fecha en String
		String fechaCompleta = anioActual + mesActual + diaActual + horaActual +
							   minutoActual + segundoActual + milisegundoActual;
		//retorno el codigo
		return _nombreJuego + fechaCompleta;
	}
	
	/**
	 * Eliminar una partida del hash de partidas (libera su slot)
	 * @param _codigoPartida
	 * @throws JuegoException
	 */
	public static void eliminarPartida(String _codigoPartida) throws JuegoException {
		if (!hashJuegos.containsKey(_codigoPartida)) {
			throw new JuegoException(JuegoException.ERROR_PARTIDA_NO_EXISTE);
		} 
		hashJuegos.remove(_codigoPartida);
	}
}